// <copyright file="PageService.cs" company="Clever Software">
// Copyright (c) 2013, 2014 All Right Reserved, http://www.cleversoftware.by
//
// This source is subject to the Microsoft Permissive License.
// Please see the License.txt file for more information.
// All other rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// </copyright>
// <author>Clever Software</author>
// <email>info@cleversoftware.by</email>
// <date>2014-02-04</date> 
// <summary>CleverCMS source code</summary>

namespace CleverCMS.Cms.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    using WebMatrix.WebData;

    using CleverCMS.Core.Constants;
    using CleverCMS.Core.Services;
    using CleverCMS.Core.Infrastructure;
    using CleverCMS.Core.Exceptions;

    using CleverCMS.Cms.Data;
    using CleverCMS.Cms.Data.Extensions;
    using CleverCMS.Cms.Data.Entities;
    using CleverCMS.Cms.Services.ViewModels;

    //TODO: Refactor this service! (Too much logic incorporated)
    public class PageService
    {
        private readonly ICmsGenericRepository<CmsStatus>           m_status;
        private readonly LoggingService                             m_log;
        private readonly IPageRepository                            m_pageRepo;
        private readonly ISettingRepository                         m_settingRepo;
        private readonly ICmsGenericRepository<CmsTag>              m_tagRepo;
        private readonly ICmsGenericRepository<CmsPageCategory>     m_catRepo;
        private readonly ICmsGenericRepository<CmsRating>           m_ratingRepository;
        private readonly ICmsGenericRepository<CmsPageComment>      m_commentsRepo;
        private readonly AppCache                                   m_appCache;

        public PageService(IPageRepository                          pageRepo,
                           ISettingRepository                       settingRepo,
                           LoggingService                           log,
                           ICmsGenericRepository<CmsTag>            tagRepo,
                           ICmsGenericRepository<CmsPageCategory>   catRepo,
                           ICmsGenericRepository<CmsStatus>         status,
                           ICmsGenericRepository<CmsRating>         rating,
                           ICmsGenericRepository<CmsPageComment>    commRepo)
        {
            this.m_pageRepo = pageRepo;
            this.m_settingRepo = settingRepo;
            this.m_log = log;
            this.m_tagRepo = tagRepo;
            this.m_catRepo = catRepo;
            this.m_status = status;
            this.m_ratingRepository = rating;
            this.m_commentsRepo = commRepo;
            this.m_appCache = new AppCache();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentId"></param>
        /// <param name="title"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public int AddNode(int parentId, string title, string type)
        {
            int id = 0;
            switch (type)
            {
                case "file":
                    m_pageRepo.Add(new CmsPage() { CategoryId = parentId, PageTitle = title });
                    m_pageRepo.Save();
                    id = m_pageRepo.LoadAll().Where(x => (x.CategoryId == parentId) && (x.PageTitle==title)).Select(y=>y.Id).FirstOrDefault();
                break;
                case "folder":
                    break;
            }
            return id;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public CmsPage GetByUrl(string name)
        {
            try
            {
                return m_pageRepo.LoadByAlias(name);
            }
            catch (Exception ex)
            {
                m_log.Warn(string.Format("Page not found: id={0}", name));
                m_log.Exception(ex);
                throw new PageNotFoundException("The requested page was not found.");
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public CmsPage GetById(int id)
        {
            try
            {
                return m_pageRepo.Load(id);
            }
            catch (Exception ex)
            {
                m_log.Warn(string.Format("Page not found: id={0}", id));
                m_log.Exception(ex);
                throw new PageNotFoundException("The requested page was not found.");
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public CmsPage LoadDefault()
        {
            try
            {
                CmsSetting defaultSetting = m_settingRepo.Get(Constant.Settings.DefaultPageId);
                return GetById(defaultSetting.GetIntValue());
            }
            catch (Exception ex)
            {
                m_log.Warn(ex.Message);
                return m_pageRepo.LoadAll().FirstOrDefault();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<CmsPage> LoadAll()
        {
            return m_pageRepo.LoadAll().OrderByDescending(x => x.ModifiedAt);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<CmsPage> LoadAll(int? id, int start, int size, string sortOrder, out int count)
        {
            var pagesQ = m_pageRepo.LoadAll(orderBy: q => q.OrderByDescending(d => d.ModifiedAt));
                                            
            if (id != null)
                pagesQ = pagesQ.Where(x => x.CategoryId == id.Value);

            count = pagesQ.Count();

            return pagesQ.Skip(start).Take(size);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<CmsPage> LoadAllPublished()
        {
            return m_pageRepo.LoadAllPublished().OrderByDescending(x => x.ModifiedAt);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<CmsPage> LoadLastCreated()
        {
            int numberOfLastPageToLoad = 10;
            
            try
            {
                CmsSetting lastCreated = m_settingRepo.Get(Constant.Settings.LastCreated);
                if (lastCreated != null)
                    numberOfLastPageToLoad = lastCreated.GetIntValue();
            }
            catch (Exception ex)
            {
                m_log.Info(ex.Message);
            }

            var lastPages = m_pageRepo.LoadLast(numberOfLastPageToLoad);
            int defaultPageid = LoadDefault().Id;
            lastPages = lastPages.Where(x => x.Id != defaultPageid);

            return lastPages;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        public IQueryable<CmsPage> GetPagesWithTag(string tag)
        {
            return m_pageRepo.LoadAll(filter: x => x.Tags.Any(y => y.Name == tag));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry"></param>
        public void Save(CmsPage entry)
        {
            entry.ModifiedAt = DateTime.Now;

            if (WebSecurity.IsAuthenticated)
                entry.AuthorId = WebSecurity.CurrentUserId;
            
            FillTags(entry);
            EnsureFieldsAreFilled(entry);
            
            m_pageRepo.Save(entry);
           
            m_appCache.Remove(new string[] { "Pages", "AdminPages" });

            if (entry.PageContent != null)
                SearchService.AddUpdateLuceneIndex(entry);
        }
        /// <summary>
        /// Add view count on 1
        /// </summary>
        /// <param name="entry"></param>
        public void AddViewCount(CmsPage entry)
        {
            
            //TODO: This needs to be reviewed and reworked to an async approach
            entry.ViewCount += 1;
            m_pageRepo.Save(entry);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry"></param>
        public void CreateEntry(CmsPage entry)
        {
            entry.ModifiedAt = DateTime.Now;
            entry.CreatedAt = DateTime.Now;
            entry.StatusId = (int)StatusCode.Draft;
            entry.AuthorId = WebSecurity.CurrentUserId;
            EnsureFieldsAreFilled(entry);
            
            m_pageRepo.Save(entry);
            m_appCache.Remove(new string[] { "Pages", "AdminPages" });
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="category"></param>
        /// <returns></returns>
        public int CreateCategory(CmsPageCategory category)
        {
            int id = 0;
            category.CreatedAt = DateTime.Now;
            m_catRepo.Save(category, true);
            m_appCache.Remove(new string[] { "Categories" });
            id = m_catRepo.LoadAll()
                    .Where(x => (x.ParentId == category.ParentId) && (x.Title == category.Title))
                    .Select(y => y.Id)
                    .First();
            return id;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void Delete(int id)
        {
            string alias = m_pageRepo.Load(id).Alias;
            m_pageRepo.Delete(id);
            m_appCache.Remove(new string[] { "Pages", "AdminPages" });
            SearchService.ClearLuceneIndexRecord(alias);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry"></param>
        private void EnsureFieldsAreFilled(CmsPage entry)
        {
            if (string.IsNullOrEmpty(entry.SeoTitle))
                entry.SeoTitle = entry.PageTitle;

            if (string.IsNullOrEmpty(entry.MetaKeywords))
                entry.MetaKeywords = entry.Tags.ToTagString();

            if (string.IsNullOrEmpty(entry.MetaDescription))
                entry.MetaDescription = entry.PageTitle;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<CmsStatus> GetPageStatuses()
        {
            return m_status.LoadAll();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageStatusId"></param>
        /// <returns></returns>
        public CmsStatus LoadPageType(int pageStatusId)
        {
            return m_status.Load(pageStatusId);
        }

        #region Tags
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void DeleteTag(int id)
        {
            m_tagRepo.Delete(id);
            m_appCache.Remove("Tags");
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="page"></param>
        private void FillTags(CmsPage page)
        {
            if (null == page)
                return;

            if (null != page.TagString && !string.IsNullOrEmpty(page.TagString.AttemptedValue))
            {
                List<String> tags = page.TagString.AttemptedValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                                                                 .Where(x => !String.IsNullOrEmpty(x))
                                                                 .Select(x => x.Trim())
                                                                 .ToList();

                foreach (string tag in tags)
                {
                    if (page.Tags.Where(x => x.Name == tag).SingleOrDefault() == null)
                    {
                        CmsTag cmsTag = m_pageRepo.LoadTag(tag);
                        if (null != cmsTag)
                            page.Tags.Add(cmsTag);
                        else
                            page.Tags.Add(new CmsTag { Name = tag.Trim() });
                    }
                }

                List<CmsTag> toDelete = page.Tags.Where(x => !tags.Contains(x.Name)).ToList();
                toDelete.ForEach(x => page.Tags.Remove(x));
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<CmsTag> GetAllTags()
        {
            return m_tagRepo.LoadAll();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<CmsTag> GetAllTags(int start, int size, string sortOrder, out int count)
        {
            var pages = m_tagRepo.LoadAll();

            if (string.IsNullOrEmpty(sortOrder) || sortOrder.Equals("Id ASC"))
            {
                pages = pages.OrderBy(p => p.Id);
            }
            else if (sortOrder.Equals("Id DESC"))
            {
                pages = pages.OrderByDescending(p => p.Id);
            }
            else if (sortOrder.Equals("Name ASC"))
            {
                pages = pages.OrderBy(p => p.Name);
            }
            else if (sortOrder.Equals("Name DESC"))
            {
                pages = pages.OrderByDescending(p => p.Name);
            }
            else
            {
                pages = pages.OrderBy(p => p.Id); 
            }
            
            count = m_tagRepo.Count();
            
            return count > 0 ? pages.Skip(start).Take(size) : pages; 
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public int GetTagCount(int id)
        {
            var tagObj = m_tagRepo.LoadAll().Where(x => x.Id == id).FirstOrDefault();
            
            if (tagObj != null)
                return tagObj.Pages.Count;
            
            return 0;
        }
        #endregion

        #region Category
        /// <summary>
        /// 
        /// </summary>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<CmsPageCategory> LoadAll(int start, int size, string sortOrder, out int count)
        {
            var cats = m_catRepo.LoadAll(orderBy: q => q.OrderByDescending(d => d.CreatedAt))
                                .Skip(start)
                                .Take(size);

            count = m_catRepo.Count();

            return cats;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<CmsPageCategory> LoadAllCategories()
        {
            var cats = m_catRepo.LoadAll(orderBy: q => q.OrderByDescending(d => d.CreatedAt));
            return cats;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public CmsPageCategory UpdateCategory(CmsPageCategory entry)
        {
            m_catRepo.Save(entry, entry.Id == 0);
            return entry;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void DeleteCategory(int id)
        {
            var category = m_catRepo.LoadAll().FirstOrDefault(x => x.Id == id);
            if (category != null)
            {
                m_catRepo.Delete(category.Id);
            }
        }
        #endregion

        #region Rating 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageId"></param>
        /// <returns></returns>
        public IQueryable<CmsRating> GetRating(int pageId)
        {
            return m_ratingRepository.LoadAll(x => (x.PageId == pageId));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="rate"></param>
        /// <param name="id"></param>
        /// <param name="userName"></param>
        /// <returns></returns>
        public PageRatingViewModel IncrementPageRating(double rate, int id, string userName)
        {
            
            if (m_ratingRepository.LoadAll(x => (x.PageId == id && x.UserId == WebSecurity.CurrentUserId)).Any())
            {
                var listOfRating = GetRating(id).ToList();
                var avg = listOfRating.Sum(x => x.Rating)/listOfRating.Count();
                return new PageRatingViewModel { TotalRaters = listOfRating.Count(), AverageRating = avg };
            }
            else
            {
                m_ratingRepository.Save(new CmsRating { PageId = id, UserId = WebSecurity.CurrentUserId, Rating = rate }, true);
                var listOfRating = GetRating(id).ToList();
                var avg = listOfRating.Sum(x => x.Rating) / listOfRating.Count();
                return new PageRatingViewModel { TotalRaters = listOfRating.Count(), AverageRating = avg };   
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool IsRatedBefore(int id, string name)
        {
            var res = m_ratingRepository.LoadAll(x => ( x.UserId == WebSecurity.CurrentUserId && x.PageId == id));
            return res != null && res.ToList().Any();
        }
        #endregion

        #region Comments
        /// <summary>
        /// Adds comment
        /// </summary>
        /// <param name="comment"></param>
        public void AddComment(CmsPageComment comment)
        {
            m_commentsRepo.Save(comment, comment.Id == 0);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageId"></param>
        /// <returns></returns>
        public ICollection<CmsPageComment> GetEntryComments(int pageId)
        {
            return m_commentsRepo.LoadAll(x => x.PageId == pageId).ToList();
        }
        /// <summary>
        /// Returns all comments for a paged list
        /// </summary>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<CmsPageComment> GetAllComments(int start, int size, string sortOrder, out int count)
        {
            var query = m_commentsRepo.LoadAll();

            //TODO: Can we use CASE here?
            if (string.IsNullOrEmpty(sortOrder) || sortOrder.Equals("Id ASC"))
            {
                query = query.OrderBy(p => p.Id);
            }
            else if (sortOrder.Equals("Id DESC"))
            {
                query = query.OrderByDescending(p => p.Id);
            }
            else if (sortOrder.Equals("Name ASC"))
            {
                query = query.OrderBy(p => p.Name);
            }
            else if (sortOrder.Equals("Name DESC"))
            {
                query = query.OrderByDescending(p => p.Name);
            }
            else if (sortOrder.Equals("Created ASC"))
            {
                query = query.OrderBy(p => p.CreatedAt);
            }
            else if (sortOrder.Equals("Created DESC"))
            {
                query = query.OrderByDescending(p => p.CreatedAt);
            }
            else
            {
                query = query.OrderBy(p => p.Id);
            }

            count = m_commentsRepo.Count();

            return count > 0 ? query.Skip(start).Take(size) : query;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void DeleteComment(int id)
        {
            m_commentsRepo.Delete(id);
        }
        #endregion

        #region Dashboard
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public int GetTotalViewCount()
        {
            var posts = m_pageRepo.LoadAll();
            return posts.Any() ? posts.Sum(x => x.ViewCount) : 0;
        }
        #endregion

    }
}